package com.beardedhen.harmonybootstrap;

import com.beardedhen.harmonybootstrap.api.attributes.BootstrapBrand;
import com.beardedhen.harmonybootstrap.api.defaults.DefaultBootstrapBrand;
import com.beardedhen.harmonybootstrap.api.defaults.DefaultBootstrapSize;
import com.beardedhen.harmonybootstrap.api.view.BootstrapBrandView;
import com.beardedhen.harmonybootstrap.api.view.BootstrapSizeView;
import com.beardedhen.harmonybootstrap.api.view.ProgressView;
import com.beardedhen.harmonybootstrap.api.view.RoundableView;
import com.beardedhen.harmonybootstrap.utils.ResUtil;
import com.beardedhen.harmonybootstrap.utils.UiUtil;
import com.beardedhen.harmonybootstrap.utils.ViewUtils;
import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.colors.RgbColor;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentParent;
import ohos.agp.components.element.Element;
import ohos.agp.components.element.PixelMapElement;
import ohos.agp.render.*;
import ohos.agp.utils.Color;
import ohos.agp.utils.Rect;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;
import ohos.media.image.PixelMap;
import ohos.media.image.common.PixelFormat;
import ohos.media.image.common.Size;

import static ohos.agp.render.Canvas.PorterDuffMode.CLEAR;
import static ohos.agp.render.Canvas.PorterDuffMode.SRC_IN;

/**
 * The type Bootstrap progress bar.
 */
public class BootstrapProgressBar extends Component implements ProgressView, RoundableView, BootstrapSizeView, BootstrapBrandView,
        AnimatorValue.ValueUpdateListener,Component.DrawTask,Component.LayoutRefreshedListener,AnimatorValue.StateChangedListener {

    private static final long UPDATE_ANIM_MS = 300;
    private static final int STRIPE_ALPHA = 150;
    private static final long STRIPE_CYCLE_MS = 1500;

    private Paint progressPaint;
    private Paint stripePaint;
    private Paint bgPaint;
    private Paint textPaint;
    private int userProgress;
    private int drawnProgress;
    private int maxProgress;
    private boolean striped;
    private boolean animated;
    private boolean rounded;

    //used for progressbarGroup so that only the currect corners will be rounded
    private boolean canRoundLeft = true;
    private boolean canRoundRight = true;

    private AnimatorValue progressAnimator;
    private Paint tilePaint;

    private static final String BOOTSTRAP_PROGRESSBAR_ANIMATED = "animated";
    private static final String BOOTSTRAP_PROGRESSBAR_ROUNDED_CORNERS = "roundedCorners";
    private static final String BOOTSTRAP_PROGRESSBAR_STRIPED = "striped";
    private static final String BOOTSTRAP_PROGRESSBAR_BOOTSTRAP_SHOW_PERCENTAGE = "bootstrapshowPercentage";
    private static final String BOOTSTRAP_PROGRESSBAR_BOOTSTRAP_PROGRESS = "bootstrapProgress";
    private static final String BOOTSTRAP_PROGRESSBAR_BOOTSTRAP_MAX_PROGRESS = "bootstrapMaxProgress";
    private static final String BOOTSTRAP_PROGRESSBAR_BOOTSTRAP_BRAND = "bootstrapBrand";
    private static final String BOOTSTRAP_PROGRESSBAR_BOOTSTRAP_SIZE = "bootstrapSize";

    /**
     * The constant mDefaultPaint.
     */
// NTB constants
    protected static Paint mDefaultPaint = new Paint() {
        {
            setAntiAlias(true);
            setDither(true);
            setFilterBitmap(true);
        }
    };

    private float baselineHeight=0f;
    private BootstrapBrand bootstrapBrand;

    private Canvas progressCanvas;
    private PixelMap progressBitmap;
    private PixelMapHolder stripeTile;

    private float bootstrapSize;
    private boolean showPercentage;
    /**
     * The Valueanimator.
     */
    AnimatorValue valueanimator=new AnimatorValue();

    /**
     * Instantiates a new Bootstrap progress bar.
     *
     * @param context the context
     */
    public BootstrapProgressBar(Context context){
        super(context);
        initialise(null);
    }

    /**
     * Instantiates a new Bootstrap progress bar.
     *
     * @param context the context
     * @param attrs   the attrs
     */
    public BootstrapProgressBar(Context context, AttrSet attrs){
        super(context, attrs);
        initialise(attrs);
    }

    /**
     * Instantiates a new Bootstrap progress bar.
     *
     * @param context      the context
     * @param attrs        the attrs
     * @param defStyleAttr the def style attr
     */
    public BootstrapProgressBar(Context context, AttrSet attrs, int defStyleAttr){
        super(context, attrs, defStyleAttr);
        initialise(attrs);
    }

    private void initialise(AttrSet attrs){
        if(attrs == null) {
            return;
        }
        setLayoutRefreshedListener(this::onRefreshed);
        addDrawTask(this::onDraw);
        valueanimator.setDelay(15); // attempt 60fps
        tilePaint = new Paint();

        progressPaint = new Paint();
        progressPaint.setStyle(Paint.Style.FILL_STYLE);
        progressPaint.setAntiAlias(true);

        stripePaint = new Paint();
        stripePaint.setStyle(Paint.Style.FILL_STYLE);
        stripePaint.setAntiAlias(true);

        textPaint = new Paint();
        textPaint.setStyle(Paint.Style.FILL_STYLE);
        textPaint.setAntiAlias(true);
        textPaint.setColor(Color.BLACK);

        textPaint.setTextSize(ResUtil.getIntDimen(getContext(), ResourceTable.Float_bootstrap_progress_bar_default_font_size));

        bgPaint = new Paint();
        bgPaint.setStyle(Paint.Style.FILL_STYLE);

        bgPaint.setColor(new Color(Color.GRAY.getValue()));


        try {
            setIsAnimated(attrs.getAttr(BOOTSTRAP_PROGRESSBAR_ANIMATED).isPresent() ?
                    attrs.getAttr(BOOTSTRAP_PROGRESSBAR_ANIMATED).get().getBoolValue() : false);

            this.rounded = (attrs.getAttr(BOOTSTRAP_PROGRESSBAR_ROUNDED_CORNERS).isPresent() ? attrs.getAttr(BOOTSTRAP_PROGRESSBAR_ROUNDED_CORNERS).get().getBoolValue() : false);

            setIsStriped(attrs.getAttr(BOOTSTRAP_PROGRESSBAR_STRIPED).isPresent() ?
                    attrs.getAttr(BOOTSTRAP_PROGRESSBAR_STRIPED).get().getBoolValue() : false);

            setShowPercentage(attrs.getAttr(BOOTSTRAP_PROGRESSBAR_BOOTSTRAP_SHOW_PERCENTAGE).isPresent() ?
                    attrs.getAttr(BOOTSTRAP_PROGRESSBAR_BOOTSTRAP_SHOW_PERCENTAGE).get().getBoolValue() : false);

            setUserProgress(attrs.getAttr(BOOTSTRAP_PROGRESSBAR_BOOTSTRAP_PROGRESS).isPresent() ?
                    attrs.getAttr(BOOTSTRAP_PROGRESSBAR_BOOTSTRAP_PROGRESS).get().getIntegerValue() : 0);
            setMaximumProgress(attrs.getAttr(BOOTSTRAP_PROGRESSBAR_BOOTSTRAP_MAX_PROGRESS).isPresent() ?
                    attrs.getAttr(BOOTSTRAP_PROGRESSBAR_BOOTSTRAP_MAX_PROGRESS).get().getIntegerValue() : 100);

            int typeOrdinal = -1;
            if (attrs.getAttr(BOOTSTRAP_PROGRESSBAR_BOOTSTRAP_BRAND).isPresent()) {
                String typeOrdinalStr = attrs.getAttr(BOOTSTRAP_PROGRESSBAR_BOOTSTRAP_BRAND).get().getStringValue().trim();
                typeOrdinal = UiUtil.getBootstrapBrandInt(typeOrdinalStr);
            }

            int sizeOrdinal = -1;
            if (attrs.getAttr(BOOTSTRAP_PROGRESSBAR_BOOTSTRAP_SIZE).isPresent()) {
                String sizeOrdinalStr = attrs.getAttr(BOOTSTRAP_PROGRESSBAR_BOOTSTRAP_SIZE).get().getStringValue().trim();
                sizeOrdinal = UiUtil.getBootstrapSizeInt(sizeOrdinalStr);
            }

            bootstrapBrand = DefaultBootstrapBrand.fromAttributeValue(typeOrdinal);
            bootstrapSize = DefaultBootstrapSize.fromAttributeValue(sizeOrdinal).scaleFactor();

            this.drawnProgress = userProgress;
        } catch (Exception e) {
            e.printStackTrace();
        }

        textPaint.setColor(new Color(bootstrapBrand.defaultTextColor(getContext())));

        textPaint.setTextSize((int) (ResUtil.getDimen(getContext(), ResourceTable.Float_bootstrap_button_default_font_size) * Math.round(this.bootstrapSize)));

        baselineHeight = ResUtil.getDimen(getContext(), ResourceTable.Float_bootstrap_progress_bar_height);
        progressAnimator=new AnimatorValue();


        updateBootstrapState();
        setProgress(this.userProgress); //overrided method
        setMaxProgress(this.maxProgress);
    }




    @Override
    public void setProgress(int progress) {
        this.userProgress = progress;

        if (animated) {
            startProgressUpdateAnimation();
        }
        else {
            this.drawnProgress = progress;
            invalidate();
        }
        ComponentParent parent = getComponentParent();
        if (parent != null) {
            if (parent instanceof BootstrapProgressBarGroup) {
                BootstrapProgressBarGroup parentGroup = (BootstrapProgressBarGroup) parent;
                parentGroup.onProgressChanged(this);
            }
        }
    }

    @Override
    public void setMaxProgress(int newMaxProgress) {
        if (getProgress() <= newMaxProgress) {
            maxProgress = newMaxProgress;
        }
        else {
            throw new IllegalArgumentException(
                    String.format("MaxProgress cant be smaller than the current progress %d<%d", getProgress(), newMaxProgress));
        }
        invalidate();
    }

    @Override
    public int getProgress() {
        return userProgress;
    }

    @Override
    public void setRounded(boolean rounded) {
        this.rounded = rounded;
        updateBootstrapState();
    }

    @Override
    public boolean isRounded() {
        return rounded;
    }

    @Override
    public void setBootstrapBrand(BootstrapBrand bootstrapBrand) {

        this.bootstrapBrand = bootstrapBrand;
        textPaint.setColor(new Color(bootstrapBrand.defaultTextColor(getContext())));
        updateBootstrapState();

    }

    @Override
    public BootstrapBrand getBootstrapBrand() {
        return bootstrapBrand;
    }

    @Override
    public float getBootstrapSize() {
        return bootstrapSize;
    }

    @Override
    public void setBootstrapSize(float bootstrapSize) {
        this.bootstrapSize = bootstrapSize;
        textPaint.setTextSize((int) ((ResUtil.getDimen(getContext(), ResourceTable.Float_bootstrap_button_default_font_size)) * this.bootstrapSize));
        postLayout();
        updateBootstrapState();

    }

    @Override
    public void setBootstrapSize(DefaultBootstrapSize bootstrapSize) {
        setBootstrapSize(bootstrapSize.scaleFactor());
    }

    @Override
    public void onUpdate(AnimatorValue animatorValue, float v) {

        drawnProgress = (int) v;
        invalidate();

    }

    @Override
    public void onDraw(Component component, Canvas canvas) {

        float w=getWidth();

        float h = (baselineHeight * bootstrapSize);
        //larger height value is returned while adding child progressbar in progressbar group

        if (w <= 0 || h <= 0) {
            return;
        }
        if (progressBitmap == null) {
            PixelMap.InitializationOptions initializationOptions = new PixelMap.InitializationOptions();
            initializationOptions.pixelFormat = PixelFormat.ARGB_8888;
            initializationOptions.size = new Size((int) w, (int) h);
            progressBitmap = PixelMap.create(initializationOptions);
        }

        if (progressCanvas == null) {
            progressCanvas =new Canvas(new Texture(progressBitmap));
        }

        progressCanvas.drawColor(Color.TRANSPARENT.getValue(), CLEAR);
        float ratio = (drawnProgress / (float) maxProgress);
        int lineEnd = (int) (w * ratio);

        float offset = 0;
        float offsetFactor = (System.currentTimeMillis() % STRIPE_CYCLE_MS) / (float) STRIPE_CYCLE_MS;

        if (striped && animated) { // determine offset for current animation frame of progress bar
            offset = (h * 2) * offsetFactor;
        }

        if (striped) { // draw a regular striped bar

            if (stripeTile == null) {
                stripeTile = createTile(h, stripePaint, progressPaint);
            }

            float start = 0 - offset;

            while (start < lineEnd) {
                progressCanvas.drawPixelMapHolder(stripeTile, start, 0, tilePaint);
                start += stripeTile.getPixelMap().getImageInfo().size.width;
            }
        }
        else { // draw a filled bar
            progressCanvas.drawRect(new RectFloat(0,0,lineEnd,h),progressPaint);
        }

        progressCanvas.drawRect(new RectFloat(lineEnd, 0, w, h),bgPaint);

        float corners = rounded ? h / 2 : 0;

        PixelMap round = createRoundedBitmap(progressBitmap, corners, canRoundRight, canRoundLeft);
        canvas.drawPixelMapHolder(new PixelMapHolder(round),0,0,tilePaint);

        if(showPercentage) {
            String percent = getProgress() + "%";
            int xPos = (lineEnd / 2);
            xPos = xPos - (int) (textPaint.measureText(percent) / 2);

            int yPos = (int) (( 60 / 2) - ((textPaint.descent() + textPaint.ascent()) / 2));
            canvas.drawText(textPaint,percent,xPos,yPos);
        }
        Element bootstrapSample= new PixelMapElement(progressBitmap);
        ViewUtils.setBackgroundDrawable(this,bootstrapSample);
    }


    @Override
    public void onRefreshed(Component component) {
        onMeasure(getWidth(), getHeight());
    }

    private void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // restrict view to default progressbar height
        int width = EstimateSpec.getSize(widthMeasureSpec);
        int height = EstimateSpec.getSize(heightMeasureSpec);

        int heightMode = EstimateSpec.getMode(heightMeasureSpec);

        switch (heightMode) {
            case EstimateSpec.NOT_EXCEED:
                break;
            case EstimateSpec.PRECISE: // prefer default height, if not all available use as much as possible
                float desiredHeight = (baselineHeight * bootstrapSize);
                height = (height > desiredHeight) ? (int) desiredHeight : height;
                break;
            default:
                height = (int) (baselineHeight * bootstrapSize);
                break;
        }
        setComponentSize(width, height);
    }

    /**
     * Creates a Bitmap which is a tile of the progress bar background
     *
     * @param h the view height
     * @param stripePaint the stripepaint
     * @param progressPaint the progresspaint
     * @return a bitmap of the progress bar background
     */
    private PixelMapHolder createTile(float h, Paint stripePaint, Paint progressPaint) {

        PixelMap.InitializationOptions initializationOptions = new PixelMap.InitializationOptions();
        initializationOptions.pixelFormat = PixelFormat.ARGB_8888;
        initializationOptions.size = new Size((int) h*2, (int) h);
        PixelMap bm = PixelMap.create(initializationOptions);
        Canvas tile = new Canvas(new Texture(bm));

        float x = 0;
        Path path = new Path();

        path.moveTo(x, 0);
        path.lineTo(x, h);
        path.lineTo(h, h);
        tile.drawPath(path, stripePaint); // draw striped triangle

        path.reset();
        path.moveTo(x, 0);
        path.lineTo(x + h, h);
        path.lineTo(x + (h * 2), h);
        path.lineTo(x + h, 0);
        tile.drawPath(path, progressPaint); // draw progress parallelogram

        x += h;
        path.reset();
        path.moveTo(x, 0);
        path.lineTo(x + h, 0);
        path.lineTo(x + h, h);
        tile.drawPath(path, stripePaint); // draw striped triangle (completing tile)

        return new PixelMapHolder(bm);
    }

    private PixelMap createRoundedBitmap(PixelMap pixelmap, float cornerRadius, boolean roundRight, boolean roundLeft) {

        int w=pixelmap.getImageInfo().size.width;
        int h=pixelmap.getImageInfo().size.height;

        PixelMap.InitializationOptions initializationOptions = new PixelMap.InitializationOptions();
        initializationOptions.pixelFormat = PixelFormat.ARGB_8888;
        initializationOptions.size = new Size((int) w, (int) h);
        PixelMap roundedBitmap = PixelMap.create(initializationOptions);

        Canvas canvas = new Canvas(new Texture(roundedBitmap));

        final Paint paint = new Paint();
        RectFloat frame1 = new RectFloat(0,0,w,h);
        final Rect leftRect = new Rect(0, 0, w / 2, h);
        final Rect rightRect = new Rect(w / 2, 0, w, h);

        // prepare canvas for transfer
        paint.setAntiAlias(true);
        paint.setColor(new Color(0xFFFFFFFF));
        paint.setStyle(Paint.Style.FILL_STYLE);
        canvas.drawColor(ResUtil.getColor(getContext(), ResourceTable.Color_bootstrap_brand_danger),SRC_IN);

        canvas.drawRoundRect(frame1, cornerRadius, cornerRadius, paint);
        if (!roundLeft){
            canvas.drawRect(new RectFloat(leftRect), paint);

        }
        if (!roundRight){
            canvas.drawRect(new RectFloat(rightRect), paint);
        }
        // draw bitmap
        paint.setBlendMode(BlendMode.SRC_IN);
        canvas.drawPixelMapHolderRect(new PixelMapHolder(pixelmap), frame1,frame1,paint);
        return roundedBitmap;
    }


    private void updateBootstrapState() {

        int color = bootstrapBrand.defaultFill(getContext());
        progressPaint.setColor(new Color(color));
        stripePaint.setColor(new Color(getStripeColor(color)));
        invalidateDrawCache();
        invalidate();
    }

    private int getStripeColor(int color) {
        RgbColor rgbColor= RgbColor.fromArgbInt(color);
        rgbColor.setAlpha(STRIPE_ALPHA);
        return rgbColor.asArgbInt();
    }

    private void invalidateDrawCache() {
        stripeTile = null;
        progressBitmap = null;
        progressCanvas = null;
    }



    /**
     * Starts an animation which moves the progress bar from one value to another, in response to
     * a call to setProgress(). Animation update callbacks allow the interpolator value to be used
     * to calculate the current progress value, which is stored in a temporary variable. The view is
     * then invalidated.
     * <p/>
     * If this method is called when a progress update animation is already running, the previous
     * animation will be cancelled, and the currently drawn progress recorded. A new animation will
     * then be started from the last drawn point.
     */
    private void startProgressUpdateAnimation() {

        progressAnimator.setDuration(UPDATE_ANIM_MS);
        progressAnimator.setLoopedCount(0);

        // start striped animation after progress update if needed
        progressAnimator.setDuration(UPDATE_ANIM_MS);
        progressAnimator.setCurveType(Animator.CurveType.DECELERATE);
        progressAnimator.setValueUpdateListener(this);
        progressAnimator.start();

    }

    private void startStripedAnimationIfNeeded() {
        if (!striped || !animated) {
            return;
        }

        progressAnimator.setDuration(UPDATE_ANIM_MS);
        progressAnimator.setLoopedCount(Animator.INFINITE);
        progressAnimator.setCurveType(Animator.CurveType.LINEAR);
        progressAnimator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float v) {
                invalidate();
            }
        });
        progressAnimator.start();
    }

    private void setMaximumProgress(int maxprogress) {
        this.maxProgress = maxprogress;
        postLayout();
    }

    private void setUserProgress(int userProgress) {
        this.userProgress = userProgress;
        postLayout();
    }

    private void setShowPercentage(boolean showpercentage) {
        this.showPercentage = showpercentage;
        postLayout();
    }

    private void setIsStriped(boolean striped) {
        this.striped = striped;
        postLayout();
    }

    private void setIsAnimated(boolean animated) {
        this.animated = animated;
        postLayout();
    }

    @Override
    public void onStart(Animator animator) {

    }

    @Override
    public void onStop(Animator animator) {

    }

    @Override
    public void onCancel(Animator animator) {

    }

    @Override
    public void onEnd(Animator animator) {

    }

    @Override
    public void onPause(Animator animator) {

    }

    @Override
    public void onResume(Animator animator) {

    }
}
